// 
// Copyright (C) 2000-2003
// magustek co.
//

#ifndef MAGUS_XTIME_HPP
#define MAGUS_XTIME_HPP

#include <magus/config.hpp>
#include <magus/cstdint.hpp>
#include <magus/thread/detail/config.hpp>

#if defined(MAGUS_HAS_FTIME)
#   include <windows.h>
#elif defined(MAGUS_HAS_GETTIMEOFDAY)
#   include <sys/time.h>
#endif

#include <cassert>

namespace magus {

enum
{
    TIME_UTC=1,
    TIME_TAI,
    TIME_MONOTONIC,
    TIME_PROCESS,
    TIME_THREAD,
    TIME_LOCAL,
    TIME_SYNC,
    TIME_RESOLUTION
};

struct xtime
{
#if defined(MAGUS_NO_INT64_T)
    int_fast32_t sec;
#else
    int_fast64_t sec;
#endif
    int_fast32_t nsec;
};

inline int xtime_get(struct xtime* xtp, int clock_type)
{
    if (clock_type == TIME_UTC)
    {
#if defined(MAGUS_HAS_FTIME)
        FILETIME ft;
        GetSystemTimeAsFileTime(&ft);
        const magus::uint64_t TIMESPEC_TO_FILETIME_OFFSET =
            ((magus::uint64_t)27111902UL << 32) +
            (magus::uint64_t)3577643008UL;
        xtp->sec = (int)((*(__int64*)&ft - TIMESPEC_TO_FILETIME_OFFSET) /
            10000000);
        xtp->nsec = (int)((*(__int64*)&ft - TIMESPEC_TO_FILETIME_OFFSET -
                              ((__int64)xtp->sec * (__int64)10000000)) * 100);
        return clock_type;
#elif defined(MAGUS_HAS_GETTIMEOFDAY)
        struct timeval tv;
        gettimeofday(&tv, 0);
        xtp->sec = tv.tv_sec;
        xtp->nsec = tv.tv_usec * 1000;
        return clock_type;
#elif defined(MAGUS_HAS_CLOCK_GETTIME)
        timespec ts;
        clock_gettime(CLOCK_REALTIME, &ts);
        xtp->sec = ts.tv_sec;
        xtp->nsec = ts.tv_nsec;
        return clock_type;
#else
#   error "xtime_get implementation undefined"
#endif
    }
    return 0;
}

inline int xtime_cmp(const xtime& xt1, const xtime& xt2)
{
    int res = (int)(xt1.sec - xt2.sec);
    if (res == 0)
        res = (int)(xt1.nsec - xt2.nsec);
    return res;
}

} // namespace magus

using namespace magus;

namespace {
	
const int MILLISECONDS_PER_SECOND = 1000;
const int NANOSECONDS_PER_SECOND = 1000000000;
const int NANOSECONDS_PER_MILLISECOND = 1000000;

const int MICROSECONDS_PER_SECOND = 1000000;
const int NANOSECONDS_PER_MICROSECOND = 1000;

inline void to_time(int milliseconds, xtime& xt)
{
    int res = 0;
    res = xtime_get(&xt, TIME_UTC);
    assert(res == TIME_UTC);

    xt.sec += (milliseconds / MILLISECONDS_PER_SECOND);
    xt.nsec += ((milliseconds % MILLISECONDS_PER_SECOND) *
        NANOSECONDS_PER_MILLISECOND);

    if (xt.nsec > static_cast<const int>(NANOSECONDS_PER_SECOND))
    {
        ++xt.sec;
        xt.nsec -= NANOSECONDS_PER_SECOND;
    }
}

#if defined(MAGUS_HAS_PTHREADS)
inline void to_timespec(const xtime& xt, timespec& ts)
{
    ts.tv_sec = static_cast<int>(xt.sec);
    ts.tv_nsec = static_cast<int>(xt.nsec);
    if(ts.tv_nsec > static_cast<const int>(NANOSECONDS_PER_SECOND))
    {
        ts.tv_sec += ts.tv_nsec / NANOSECONDS_PER_SECOND;
        ts.tv_nsec %= NANOSECONDS_PER_SECOND;
    }
}

inline void to_timespec(int milliseconds, timespec& ts)
{
    xtime xt;
    to_time(milliseconds, xt);
    to_timespec(xt, ts);
}

inline void to_timespec_duration(const xtime& xt, timespec& ts)
{
    xtime cur;
    int res = 0;
    res = xtime_get(&cur, TIME_UTC);
    assert(res == TIME_UTC);

    if (xtime_cmp(xt, cur) <= 0)
    {
        ts.tv_sec = 0;
        ts.tv_nsec = 0;
    }
    else
    {
        ts.tv_sec = xt.sec - cur.sec;
        ts.tv_nsec = xt.nsec - cur.nsec;

        if( ts.tv_nsec < 0 )
        {
            ts.tv_sec -= 1;
            ts.tv_nsec += NANOSECONDS_PER_SECOND;
        }
        if(ts.tv_nsec > static_cast<const int>(NANOSECONDS_PER_SECOND))
        {
            ts.tv_sec += ts.tv_nsec / NANOSECONDS_PER_SECOND;
            ts.tv_nsec %= NANOSECONDS_PER_SECOND;
        }
    }
}
#endif // MAGUS_HAS_PTHREADS

inline void to_duration(xtime xt, int& milliseconds)
{
    xtime cur;
    int res = 0;
    res = xtime_get(&cur, TIME_UTC);
    assert(res == TIME_UTC);

    if (xtime_cmp(xt, cur) <= 0)
        milliseconds = 0;
    else
    {
        if (cur.nsec > xt.nsec)
        {
            xt.nsec += NANOSECONDS_PER_SECOND;
            --xt.sec;
        }
        milliseconds = (int)((xt.sec - cur.sec) * MILLISECONDS_PER_SECOND) +
            (((xt.nsec - cur.nsec) + (NANOSECONDS_PER_MILLISECOND/2)) /
                NANOSECONDS_PER_MILLISECOND);
    }
}

inline void to_microduration(const xtime& xt, int& microseconds)
{
    xtime cur;
    int res = 0;
    res = xtime_get(&cur, TIME_UTC);
    assert(res == TIME_UTC);

    if (xtime_get(&cur, TIME_UTC) <= 0)
        microseconds = 0;
    else
    {
        microseconds = (int)((xt.sec - cur.sec) * MICROSECONDS_PER_SECOND) +
            (((xt.nsec - cur.nsec) + (NANOSECONDS_PER_MICROSECOND/2)) /
                NANOSECONDS_PER_MICROSECOND);
    }
}

} // namespace dummy

// Change Log:
//    16 JUN 03  Initial version.

#endif // MAGUS_XTIME_HPP

